#!/usr/bin/env python3
"""
文件上传工具模块
处理图片和3D模型文件的上传、验证和存储
"""

import os
import uuid
import aiofiles
from pathlib import Path
from typing import Optional, Dict, Any
from fastapi import UploadFile, HTTPException
from PIL import Image
import json


class FileUploadManager:
    """文件上传管理器"""
    
    def __init__(self, upload_dir: str = "uploads"):
        self.upload_dir = Path(upload_dir)
        self.upload_dir.mkdir(exist_ok=True)
        
        # 创建子目录
        self.image_dir = self.upload_dir / "images"
        self.model_dir = self.upload_dir / "models"
        self.temp_dir = self.upload_dir / "temp"
        
        self.image_dir.mkdir(exist_ok=True)
        self.model_dir.mkdir(exist_ok=True)
        self.temp_dir.mkdir(exist_ok=True)
        
        # 文件类型配置
        self.allowed_image_types = {
            'image/jpeg': '.jpg',
            'image/jpg': '.jpg', 
            'image/png': '.png',
            'image/gif': '.gif',
            'image/webp': '.webp'
        }
        
        self.allowed_model_types = {
            'model/gltf-binary': '.glb',
            'model/gltf+json': '.gltf',
            'application/octet-stream': '.obj',
            'model/stl': '.stl',
            'model/x-stl': '.stl',
            'application/sla': '.stl',
            'model/fbx': '.fbx',
            'application/fbx': '.fbx',
            'model/dae': '.dae',
            'model/x-collada': '.dae'
        }
        
        # 文件大小限制 (10MB)
        self.max_image_size = 10 * 1024 * 1024
        self.max_model_size = 50 * 1024 * 1024
    
    def generate_filename(self, original_filename: str, file_type: str) -> str:
        """生成唯一文件名"""
        ext = Path(original_filename).suffix.lower()
        unique_id = str(uuid.uuid4())
        return f"{unique_id}{ext}"
    
    def validate_image_file(self, file: UploadFile) -> Dict[str, Any]:
        """验证图片文件"""
        # 检查文件类型
        if file.content_type not in self.allowed_image_types:
            raise HTTPException(
                status_code=400, 
                detail=f"不支持的图片格式。支持的格式: {', '.join(self.allowed_image_types.keys())}"
            )
        
        # 检查文件大小
        file.file.seek(0, 2)  # 移动到文件末尾
        file_size = file.file.tell()
        file.file.seek(0)  # 重置文件指针
        
        if file_size > self.max_image_size:
            raise HTTPException(
                status_code=400,
                detail=f"图片文件太大。最大允许大小: {self.max_image_size / 1024 / 1024}MB"
            )
        
        # 验证图片格式
        try:
            file.file.seek(0)
            image = Image.open(file.file)
            image.verify()  # 验证图片格式
            file.file.seek(0)  # 重置文件指针
        except Exception:
            raise HTTPException(
                status_code=400,
                detail="无效的图片文件"
            )
        
        return {
            'valid': True,
            'file_size': file_size,
            'extension': self.allowed_image_types[file.content_type],
            'dimensions': image.size if 'image' in locals() else None
        }
    
    def validate_3d_model_file(self, file: UploadFile) -> Dict[str, Any]:
        """验证3D模型文件"""
        # 检查文件类型
        content_type = file.content_type or ''
        
        # 如果没有content_type，尝试从文件扩展名判断
        if not content_type:
            ext = Path(file.filename).suffix.lower()
            content_type_map = {
                '.glb': 'model/gltf-binary',
                '.gltf': 'model/gltf+json',
                '.obj': 'application/octet-stream',
                '.stl': 'model/stl',
                '.fbx': 'model/fbx',
                '.dae': 'model/dae'
            }
            content_type = content_type_map.get(ext, '')
        
        if content_type not in self.allowed_model_types:
            raise HTTPException(
                status_code=400,
                detail=f"不支持的3D模型格式。支持的格式: {', '.join(self.allowed_model_types.keys())}"
            )
        
        # 检查文件大小
        file.file.seek(0, 2)
        file_size = file.file.tell()
        file.file.seek(0)
        
        if file_size > self.max_model_size:
            raise HTTPException(
                status_code=400,
                detail=f"3D模型文件太大。最大允许大小: {self.max_model_size / 1024 / 1024}MB"
            )
        
        return {
            'valid': True,
            'file_size': file_size,
            'extension': self.allowed_model_types[content_type]
        }
    
    async def save_image_file(self, file: UploadFile, subdir: str = "products") -> Dict[str, str]:
        """保存图片文件"""
        # 验证文件
        validation_result = self.validate_image_file(file)
        
        # 生成文件名
        filename = self.generate_filename(file.filename, 'image')
        
        # 创建子目录
        save_dir = self.image_dir / subdir
        save_dir.mkdir(exist_ok=True)
        
        file_path = save_dir / filename
        
        # 保存文件
        try:
            async with aiofiles.open(file_path, 'wb') as f:
                content = await file.read()
                await f.write(content)
            
            # 生成缩略图
            thumbnail_filename = f"thumb_{filename}"
            thumbnail_path = save_dir / thumbnail_filename
            await self.create_thumbnail(file_path, thumbnail_path)
            
            return {
                'filename': filename,
                'file_path': str(file_path.relative_to(self.upload_dir)),
                'thumbnail_path': str(thumbnail_path.relative_to(self.upload_dir)),
                'file_size': str(validation_result['file_size']),
                'url': f"/uploads/images/{subdir}/{filename}",
                'thumbnail_url': f"/uploads/images/{subdir}/{thumbnail_filename}"
            }
            
        except Exception as e:
            # 如果保存失败，清理文件
            if file_path.exists():
                file_path.unlink()
            if thumbnail_path.exists():
                thumbnail_path.unlink()
            raise HTTPException(
                status_code=500,
                detail=f"保存图片文件失败: {str(e)}"
            )
    
    async def save_3d_model_file(self, file: UploadFile, subdir: str = "products") -> Dict[str, str]:
        """保存3D模型文件"""
        # 验证文件
        validation_result = self.validate_3d_model_file(file)
        
        # 生成文件名
        filename = self.generate_filename(file.filename, 'model')
        
        # 创建子目录
        save_dir = self.model_dir / subdir
        save_dir.mkdir(exist_ok=True)
        
        file_path = save_dir / filename
        
        # 保存文件
        try:
            async with aiofiles.open(file_path, 'wb') as f:
                content = await file.read()
                await f.write(content)
            
            return {
                'filename': filename,
                'file_path': str(file_path.relative_to(self.upload_dir)),
                'file_size': str(validation_result['file_size']),
                'url': f"/uploads/models/{subdir}/{filename}",
                'format': validation_result['extension'].replace('.', '')
            }
            
        except Exception as e:
            # 如果保存失败，清理文件
            if file_path.exists():
                file_path.unlink()
            raise HTTPException(
                status_code=500,
                detail=f"保存3D模型文件失败: {str(e)}"
            )
    
    async def create_thumbnail(self, source_path: Path, thumbnail_path: Path, size: tuple = (300, 300)):
        """创建图片缩略图"""
        try:
            with Image.open(source_path) as image:
                # 保持宽高比缩放
                image.thumbnail(size, Image.Resampling.LANCZOS)
                image.save(thumbnail_path, quality=85, optimize=True)
        except Exception as e:
            print(f"创建缩略图失败: {e}")
            # 如果缩略图创建失败，复制原文件
            import shutil
            shutil.copy2(source_path, thumbnail_path)
    
    def delete_file(self, file_path: str) -> bool:
        """删除文件"""
        try:
            full_path = self.upload_dir / file_path
            if full_path.exists():
                full_path.unlink()
                return True
            return False
        except Exception as e:
            print(f"删除文件失败: {e}")
            return False
    
    def get_file_info(self, file_path: str) -> Optional[Dict[str, Any]]:
        """获取文件信息"""
        try:
            full_path = self.upload_dir / file_path
            if not full_path.exists():
                return None
            
            stat = full_path.stat()
            return {
                'size': stat.st_size,
                'created': stat.st_ctime,
                'modified': stat.st_mtime,
                'path': str(full_path),
                'relative_path': file_path
            }
        except Exception as e:
            print(f"获取文件信息失败: {e}")
            return None
    
    def cleanup_temp_files(self, max_age_hours: int = 24):
        """清理临时文件"""
        import time
        
        current_time = time.time()
        max_age_seconds = max_age_hours * 3600
        
        try:
            for file_path in self.temp_dir.rglob("*"):
                if file_path.is_file():
                    file_age = current_time - file_path.stat().st_mtime
                    if file_age > max_age_seconds:
                        file_path.unlink()
                        print(f"删除临时文件: {file_path}")
        except Exception as e:
            print(f"清理临时文件失败: {e}")


# 全局文件上传管理器实例
file_manager = FileUploadManager()


def get_file_manager() -> FileUploadManager:
    """获取文件上传管理器实例"""
    return file_manager